// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using CIP4.Common;
using CIP4.Item;
using CIP4.Simple;

namespace CIP4.Browser
{
    [CIP4Item(AddAllProperties=true, Collection="CIP4BrowserViews")]
    public class ViewType
    {
        private static StrongWrapper<ViewType> _Wrapper;
        public static StrongWrapper<ViewType> Wrapper
        {
            get
            {
                if (_Wrapper == null)
                {
                    _Wrapper = new StrongWrapper<ViewType>(MainBrowserForm.TheForm.Logger);
                    _Wrapper.GetValues = GetWrapperConnectionValues;
                }
                return _Wrapper;
            }
        }

        private static ProviderTypesEnum GetWrapperConnectionValues(Dictionary<string, string> values)
        {
            string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CIP4Browser");
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            path = Path.Combine(path, "CIP4BrowserViews.xml");
            values.Add("XmlFileName", path);
            return ProviderTypesEnum.XmlFile;
        }

        private ProviderTypesEnum GetConnectionValues(Dictionary<string, string> values)
        {
            foreach (PropertyInfo prop in Connection.GetType().GetProperties())
            {
                object value = prop.GetValue(Connection, new object[] { });
                if (value != null && !(prop.Name == "ProviderKind" || prop.Name == "ConnectionString"))
                    values.Add(prop.Name, value.ToString());
            }
            return Connection.ProviderKind;
        }

        [CIP4ExternalID, CIP4Property(DontSerialize=true)]
        public string ExternalID { get { return this.ToString(); } }

        [CIP4Property(DontSerialize = true), Browsable(false)]
        public LoggerDelegate Logger { get; private set; }

        public string AssemblyPath { get;set; }

        [CIP4Property(SerializeTarget=true)]
        public CIP4ConnectionAttribute Connection { get; set; }

        public string CurrentType { get; set; }

        public string CollectionName { get; set; }

        public int BatchSize { get; set; }

        public string CurrentTemplate { get; set; }

        private Assembly _CurrentAssembly;

        public ViewType(LoggerDelegate logger)
        {
            Logger = logger;
        }

        public ViewType()
        {
            BatchSize = 10;
        }

        public void SetLogger(LoggerDelegate logger)
        {
            Logger = logger;
        }

        public Type GetViewType()
        {
            try
            {
                return GetViewAssembly().GetTypes().FirstOrDefault(t => t.Name == CurrentType);
            }
            catch (Exception ex)
            {
                Logger(MajorEnum.Error, MinorEnum.Generic, "Unable to load type {0}: {1}", new object[] { CurrentType, ex });
                return null;
            }
        }

        public void SetViewType(Type aType)
        {
            if (!GetViewAssembly().GetTypes().Any(t => t == aType))
            {
                Logger(MajorEnum.Error, MinorEnum.Generic, "The given type ({0}) is not from the view's assembly: {1}", new object[] { aType.Name, AssemblyPath });
                return;
            }
            CurrentType = aType.Name;
            CIP4ItemAttribute att = CIP4ItemAttribute.GetAttribute(aType);
            if (att != null)
            {
                CollectionName = att.Collection;
            }
            else
            {
                Logger(MajorEnum.Warning, MinorEnum.Generic, "OK: but Given type ({0}) does not have a CIP4ItemAttribute", new object[] { aType.Name });
            }
            CIP4ConnectionAttribute connAtt = CIP4ConnectionAttribute.GetAttribute(aType);

            if (connAtt != null)
                Connection = connAtt;

        }

        [CIP4Property(DontSerialize=true)]
        public string ExternalIDPropertyName
        {
            get
            {
                Type current = GetViewType();
                if (current == null)
                    return string.Empty;
                foreach (PropertyInfo prop in current.GetProperties())
                {
                    CIP4ExternalIDAttribute att = (CIP4ExternalIDAttribute)prop.GetCustomAttributes(false).FirstOrDefault(a => a is CIP4ExternalIDAttribute);
                    if (att != null)
                        return prop.Name;
                }
                return string.Empty;
            }
        }

        public Assembly GetViewAssembly()
        {
            if (_CurrentAssembly == null || _CurrentAssembly.Location != AssemblyPath)
            {
                try
                {
                    _CurrentAssembly = Assembly.LoadFile(AssemblyPath);
                }
                catch (Exception ex)
                {
                    Logger(MajorEnum.Error, MinorEnum.Generic, "Error loading assembly: {0}", new object[] { ex });
                }
            }
            return _CurrentAssembly;
        }

        public object StrongWrapper { get; private set; }

        public void ResetStrongWrapper()
        {
            if (StrongWrapper != null)
                ((IDisposable)StrongWrapper).Dispose();
            StrongWrapper = null;
        }
        
        private object GetStrongWrapper()
        {
            if (StrongWrapper == null)
            {
                CIP4ItemAttribute oldItemAtt = CIP4ItemAttribute.GetAttribute(GetViewType());

                CollectionName = 
                    !string.IsNullOrEmpty(CollectionName)? CollectionName : 
                    oldItemAtt == null? GetViewType().Name : oldItemAtt.Collection;

                CIP4ItemAttribute itemAtt = new CIP4ItemAttribute
                {
                    Collection = CollectionName,
                    AddAllProperties = oldItemAtt == null? true: oldItemAtt.AddAllProperties,
                    ActualName = oldItemAtt == null? GetViewType().Name: oldItemAtt.ActualName,
                };

                Type[] typeArgs = { GetViewType() };
                Type strongWrapperType = typeof(StrongWrapper<>).MakeGenericType(typeArgs);
                StrongWrapper = Activator.CreateInstance(strongWrapperType, new object[] { itemAtt, Logger });
                PropertyInfo getValuesProperty = strongWrapperType.GetProperty("GetValues");
                getValuesProperty.SetValue(StrongWrapper, new GetConnectionValuesDelegate(GetConnectionValues), new object[] { });
            }
            return StrongWrapper;
        }

        public IEnumerable<object> GetItems()
        {
            Type[] typeArgs = { GetViewType() };
            Type strongWrapperType = typeof(StrongWrapper<>).MakeGenericType(typeArgs);
            MethodInfo getItems = strongWrapperType.GetMethod("GetItems", new Type[] { typeof(IBooleanValue), typeof(LoggerDelegate)} );
            object[] methodArgs = { null, Logger };

            Type strongListType = typeof(IEnumerable<>).MakeGenericType(typeArgs);
            MethodInfo getEnumerator = strongListType.GetMethod("GetEnumerator");


            Type strongEnumeratorType = typeof(IEnumerator<>).MakeGenericType(typeArgs);
            MethodInfo moveNextMethod = strongEnumeratorType.GetInterfaces()[1].GetMethod("MoveNext");
            PropertyInfo currentProperty = strongEnumeratorType.GetProperty("Current");

            object result = getItems.Invoke(GetStrongWrapper(), methodArgs);
            object objectEnumerator = getEnumerator.Invoke(result, new object[] { });

            while ((bool)moveNextMethod.Invoke(objectEnumerator, new object[] { }))
            {
                yield return currentProperty.GetValue(objectEnumerator, new object[] { });
            }
            yield break;
        }

        public void SaveItems(List<object> items)
        {
            Type[] typeArgs = { GetViewType() };
            Type strongWrapperType = typeof(StrongWrapper<>).MakeGenericType(typeArgs);
            MethodInfo saveItems = strongWrapperType.GetMethod("SaveItems");
            Type strongListType = typeof(List<>).MakeGenericType(typeArgs);
            MethodInfo addMethod = strongListType.GetMethod("Add");
            object strongList = Activator.CreateInstance(strongListType);
            foreach (object item in items)
                addMethod.Invoke(strongList, new object[] { item });
            object[] methodArgs = { strongList, Logger };
            saveItems.Invoke(GetStrongWrapper(), methodArgs);
        }

        public void PurgeItems(List<object> items)
        {
            Type[] typeArgs = { GetViewType() };
            Type strongWrapperType = typeof(StrongWrapper<>).MakeGenericType(typeArgs);
            MethodInfo purgeItems = strongWrapperType.GetMethod("PurgeItems");
            Type strongListType = typeof(List<>).MakeGenericType(typeArgs);
            MethodInfo addMethod = strongListType.GetMethod("Add");
            object strongList = Activator.CreateInstance(strongListType);
            foreach (object item in items)
                addMethod.Invoke(strongList, new object[] { item });
            object[] methodArgs = { strongList, Logger };
            purgeItems.Invoke(GetStrongWrapper(), methodArgs);
        }

        public object GetObjectForXml(string xmlText)
        {
            try
            {
                Type[] typeArgs = { GetViewType() };
                Type strongWrapperType = typeof(StrongWrapper<>).MakeGenericType(typeArgs);
                MethodInfo objectForXml = strongWrapperType.GetMethod("GetObjectForXml");
                return objectForXml.Invoke(GetStrongWrapper(), new object[] { XElement.Parse(xmlText) });
            }
            catch (Exception ex)
            {
                Logger(MajorEnum.Information, MinorEnum.Generic, "because of {0} Unable to get object of type {1} for the given xml: {2}",
                    new object[] { ex.Message , GetViewType().Name, xmlText});
                return null;
            }
        }

        public string GetXmlForObject(object forObject)
        {
            try
            {
                Type[] typeArgs = { GetViewType() };
                Type strongWrapperType = typeof(StrongWrapper<>).MakeGenericType(typeArgs);
                MethodInfo xmlForObject = strongWrapperType.GetMethod("GetXmlForObject");
                object result = xmlForObject.Invoke(GetStrongWrapper(), new object[] { forObject });
                return result.ToString();
            }
            catch (Exception ex)
            {
                Logger(MajorEnum.Information, MinorEnum.Generic, "Unable to get xml for the given object of type {0} type expected is {1} because {2}",
                    new object[] { forObject.GetType().Name, GetViewType().Name, ex.Message });
                return null;
            }
        }

        private string GetProviderName()
        {
            if (Connection != null)
                return Connection.ProviderKind.ToString();
            return string.Empty;
        }

        string lastXmlForThisView;

        public void Clean()
        {
            lastXmlForThisView = Wrapper.GetXmlForObject(this);
        }

        [CIP4Property(DontSerialize=true)]
        public bool IsDirty
        {
            get
            {
                return ScrubIt("CurrentItem", ScrubIt("ModifiedDate", lastXmlForThisView)) != 
                    ScrubIt("CurrentItem", ScrubIt("ModifiedDate", Wrapper.GetXmlForObject(this)));
            }
        }

        private static string ScrubIt(string target, string text)
        {
            if (string.IsNullOrEmpty(text))
                return string.Empty;
            XElement element = XElement.Parse(text);
            foreach(XElement sub in element.Descendants())
                if (sub.Name.LocalName == target)
                {
                    sub.Remove();
                    return element.ToString();
                }
            return text;
        }



        public override string ToString()
        {
            if (string.IsNullOrEmpty(CurrentType))
                return AssemblyPath;
            else
                return string.Format("{0} from {1} in {2} using {3}", CurrentType, Path.GetFileName(AssemblyPath), CollectionName, GetProviderName());
        }
    }
}
